APPENDIX E
Previous Section Table of Contents Index Errata

The LScript Preprocessor

What Is A Preprocessor?

As the name implies, a preprocessor is something that processes and potentially modifies some type of input before passing it on to another process. In this respect, a preprocessor is a filter through which input passes, possibly emerging in a state altogether different from its original form.

How Do I Tell LScript To Use The Preprocessor?

You don't.

Each LScript that you select for execution is automatically filtered through the preprocessor, whether or not it contains any preprocessor-specific instructions or commands. The preprocessor is simply part of the process of compiling a script for execution.

Preprocessor Features

The LScript preprocessor mechanism provides three major features to LScript writers: Compiler Directives, Insert Files and Preprocessor Macros (technically, in preprocessor parlance, these are referred to as "manifest constants").

All preprocessor directives in an LScript begin with an AT sign ('@') in column 1, followed by the directive name. For example:

        // A simple script to rotate an object

        @warnings
        ...
In older versions of LScript, the #pragma keyword was employed to provide some of this functionality, and it can still appear in LScript files. However, it has become obsolete, and support for it will be dropped in a future release of the LScript engine. It's use is therefore discouraged.

Compiler Directives

A compiler directive is an instruction you give to a compiler or interpreter to modify or affect its behavior. In LScript, each compiler directive applies only to the LScript in which it appears.

The following compiler directives are recognized by the LScript preprocessor:


        warnings [count]
        
            The warnings directive instructs LScript to bypass the
            display of run-time warning messages.  The optional count
            parameter is an integer value that indicates the maximum number
            of warnings that will be displayed.

            Warning messages could be generated when a loss of data or an
            unusual programming circumstance is detected by the
            LScript engine during script execution.  For instance, assigning
            the return values of a user-defined procedure to an array
            whose size is less than the count of return arguments is
            always guaranteed to generate a warning message during an
            LScript execution.

            Warnings are turned off automatically in compiled scripts.


        errors count

            The errors directive provides to LScript the maximum
            number of run-time error messages that will be displayed.
            The count parameter is an integer value, and must be
            provided.  Error messages cannot be turned off, and the
            count parameter cannot be zero (0).

            Error messages are enabled in compiled scripts.


        unused

            During script development, it's possible to clutter your script
            with unused variables.  In large scripts, it can take a great
            deal of time to locate and purge these "dead" variables.

            The unused directive causes LScript to detect and report on
            declared variables that are never used (i.e., assigned a value).
            When your script has completed execution, LScript will display
            a dialog window for each variable that it determined to be
            unused.

            This directive is intended as a support feature when the
            strict directive is enabled, and then only as a
            cleanup measure when script development is nearing completion.
            It is not supported in compiled scripts nor in "lax" declaration
            mode.


        version {major.minor | "major.minor"}

            The LScript engine provides for allowing scripts to specify
            minimum versions of the LScript plug-in to be used to execute
            the script.  This is useful for scripts that have taken advantage
            of newer features that would cause the script to fail to compile
            on earlier versions of the plug-in.

            Version indicators can be either literal floating-point values, or
            literal string values.  Only major and minor values, separated by
            a period ('.'), will be considered valid by the interpreter.


        autoerror

            The normal practice for the LScript engine is to provide the
            return value of all Layout- or Modeler-specific commands and
            functions to the script.  This allows the script to determine
            if a command failed, and take actions that it deems appropriate.
            However, there may be situations where the script programmer
            does not care to add the extra code required to manage these
            potential error conditions.  In this case, the script programmer
            can specify the autoerror directive.  This directive instructs
            LScript to handle these error conditions automatically, by issuing
            an error message to the user, and terminating the script.


        asyncspawn

            LScript has the ability to launch external processes during a
            script's execution (see the example LScript batch.ls for
            an illustration of this capability).  The default behavior of
            the LScript engine is to execute these processes in a synchronous
            fashion (i.e., halting execution of the LScript until the external
            process has completed).  However, there may be times when a
            script programmer wishes to interact with an external process.
            In this case, the script programmer needs to direct LScript to
            launch external processes asynchronously, causing them to operate
            in parallel with the LScript.  The asyncspawn directive is
            used in this situation.


        nonrandom

            The nonrandom directive will disable the random access to
            ASCII text file lines that LScript affords to script programmers
            through the use of its line() File Object Agent method.  When
            specified, LScript will not preprocess text files (those opened in
            ASCII mode) to determine line offsets.  Attempts to access the
            line() method in a script where this pragma is employed will
            result in a run-time error.

            If your script tends to deal with large text files without the need
            to randomly access lines, utilizing this directive will appreciably
            increase the overall execution speed of your script.


        literalpaths

            Because LScript operates across (currently) three very-different
            operating systems, it will automatically attempt to massage file
            paths provided to functions suchs as matchfiles() and getfile()
            into something more suitable for the current operating system.  For
            instance, under the Macintosh OS, UNIX forward slashes (/) and
            MS-DOS back slashes (\) are converted into colons (:) because that
            is the standard used by the Mac to separate directories.

            The literalpaths directive disables this internal processing,
            and causes the LScript engine to use file paths as they appear in
            the script.


        strict

            LScript allows you to simply use variables and arrays whereever and
            whenever you need them without having to explicitly declare them in
            a previous 'var' statement (and in the case of arrays, without having
            had to first explicitly declare an upper limit).  However, there may be
            times when you wish to add more structure to your script development
            by requiring all variables and arrays to be explicitly declared.  The
            strict directive switches the LScript engine into a behavior
            where variables and arrays are required to be explicitly declared
            or sized in a var statement.

            Regardless of the current declaration mode, multi-dimensional arrays
            must always be explicitly declared.

Insert Files

Files can be "inserted" into your LScript at the time that it is compiled by the LScript engine. These inserted files look to the engine as though they were simply part of the original script (in fact, the preprocessor actually makes them part of the original script before passing the final script off to the engine). Inserted files can contain anything that a standard LScript can house, including preprocessor commands.

Insert files are used to house common commands and code that you wish to make availble to two or more LScripts. This prevents you from not only having to duplicate script code across all LScripts sharing the insert file, but greatly reduces the likelihood that you will alter the functionality or meaning of data as you duplicate it. Housing common code in an insert file also provides the advantage that updates can be performed just once to all dependent scripts, in a single location.

You can insert files into your original scripts (and into other insert files, for that matter, up to 30 levels deep!) by using the insert directive followed by a string literal that specifies the name of the file to be inserted:

        ...
        @insert "myinc.lsi"
        ...
If an insert file does not contain an absolute path, the LScript preprocessor will attempt to locate the script using the following steps:

        1.  Look in the current directory (which may or may not be the
            same location as the script being executed)

        2.  Look in each directory specified in the LibPath configuration
            element
If an insert file cannot be located, the LScript preprocessor will halt with an error message, and the LScript engine will terminate.

Preprocessor directives continue to be evaluated and accumulated as each insert file is processed.

The LScript preprocessor imposes no naming conventions on your include files. They can be named in any fashion you wish, as long as the names are accurately provided to the insert directive.

Preprocessor Macros

Preprocessor macros (or, manifest constants to the hard-core software developer) behave very much like aliases. When a macro identifier is encountered in your script code by the preprocessor, it substitutes the value of that macro identifier for the identifier itself. This can prove handy for localizing code changes (instead of having to change the value itself in numerous locations in the script), and for making your script code more readable (i.e., "SELECT_SPHERE" is a bit easier to understand than "566").

Macros can also be embedded, which means that if a macro identifier appears in the value of another macro identifier, then a substitution will take place on the embedded identifier before the main identifier is substituted in the script code.

Preprocess macros are defined using the define directive, followed by the macro identifier name and the value to be assigned to that identifier:

        ...
        @define TOTAL_LIGHTS  100
        ...
In the above example, the identifier "TOTAL_LIGHTS" is assigned a value of 100. If this identifier is encountered anywhere in the script code (or within code found in a file inserted after the definition), the LScript preprocessor will substitute the value in the output code before passing it to the LScript engine to be compiled.

Therefore, if the following line of script code were processed after the definition of TOTAL_LIGHTS:

        ...
        for(x = 0;x < TOTAL_LIGHTS;x++)
        ...
the LScript engine would actually see:
        ...
        for(x = 0;x < 100;x++)
        ...
Macro substitution occurs in the preprocessor before any other processing takes place.

High Visibility

LScript preprocessor macros have what is known as "high visibility." This means that, while locating identifiers, the preprocess will not regard the context of the identifier. Unless the identifier appears between open and close quotation marks (""), the preprocessor will perform a substitution. This is contrary to traditional preprocessors that require the identifier to be in a stand-alone context before substitution will occur. Typically, punctuation marks and/or whitespace surrounding the identifier are consider sufficient stand-alone context by traditional preprocessors.

Let's see an example of this. If the following line of code were passed to a traditional preprocessor (for instance, a C preprocessor), a compile-time error would occur because the embedded identifier does not exist in a context wherein it can be consider stand-alone (in other words, it is part of another identifier, and becomes therefore inelligible):

        #define ONEFIFTY   15
        ...
        x = ONEFIFTY0;      // <--- error!  no such identifier "ONEFIFTY0"
However, LScript's preprocessor would handle the situation differently:
        @define ONEFIFTY  15
        ...
        x = ONEFIFTY0;      // <--- assigns the value 150 to x
This method of high-visibility substitution can be powerful, but can also cause you many difficulties if you happen to use script variable and function identifiers that contain embedded macro names. Please keep this in mind while writing your LScripts.

Putting It All Together

Here is a rather-convoluted example that is both fully-functional and representative of the capabilities of the LScript preprocessor.

        @define SELECT 15
        @define SELECT2 string("SELECT ",SELECT0)
        @define SELECT3 "Bob!"

        @define VERSION_DIRECTIVE version 1.0

        @define BEGIN_BOBS_MAIN main {
        @define END_BOBS_MAIN }
        @define REM //

        @insert "includes.h"

        @VERSION_DIRECTIVE
        @warnings

        BEGIN_BOBS_MAIN
            info(SELECT2);            REM prints "SELECT 150"
            info(SELECT2 + SELECT3);  REM using string math, prints "SELECT 150Bob!"
        END_BOBS_MAIN

Previous Section Table of Contents Index Errata
© 1996 Virtual Visions, Inc.
© 1997 NewTek, Inc.